from querier.esquerier import ElasticSearchQuerier
import utils.utils as uc
import querier.weibo.utils as utils


MINIMUM_SHOULD_MATCH = '5<85% 10<9'
MAX_BRANDS = 40
MAX_CHARACTER = 50
MAX_KEYWORDS = 10
MAX_CATEGORY = 16
CATEGORY_CUTOFF = 0.2


class WeiboKOLMatchQuerier(ElasticSearchQuerier):
    def __init__(self, es, index, doc_type, nlp_service, engagement_info_querier):
        super(WeiboKOLMatchQuerier, self).__init__(es, index, doc_type)
        self.nlp_service = nlp_service
        self.engagement_info_querier = engagement_info_querier

    def _build_query(self, args):
        """输入 文案, 节日, 热点事件, 类目, 返回匹配KOL
        :param args:
        :return:
        """

        # 文案
        term = args.get('term', '')
        term = term if term else ''

        # category in filters
        filters = args.get('filters', {})
        if filters is None:
            filters = {}
        order = args.get('order_by', utils.ORDER_OVERALL)
        from_ = args.get('from', 0)
        size_ = args.get('size', 10)
        highlight = args.get('highlight', False)
        backend = args.get('backend', False)

        filter_keywords = args.get('filter_keywords')
        filter_keywords = filter_keywords if filter_keywords else []

        # 处理查询文本
        term2, keywords, ex_keywords, weights = utils.process_query_term(term, self.nlp_service)

        ex_kw = utils.get_kv_json(ex_keywords, weights)
        ex_category = self.nlp_service.classify(keywords)
        ex_category = ex_category.get('classify', {})
        ex_category = utils.get_kv_json(ex_category.get('category', []), ex_category.get('category_prob', []))
        ex_category = ex_category[0:MAX_CATEGORY]
        keywords = keywords[0:MAX_KEYWORDS]

        query = self._gen_query(term, ' '.join(keywords[0:MAX_KEYWORDS]), filters, filter_keywords,
                                order, from_, size_, highlight)

        return query, {}, {'keywords': keywords, 'order': order, 'ex_keywords': ex_kw,
                           'ex_category': ex_category, 'backend': backend, 'filter_keywords': filter_keywords}

    def _build_result(self, es_result, param):
        words = []
        for w in param['filter_keywords']:
            for k in w:
                words.append(k)

        keywords = param['keywords']
        # order = param['order']

        total = es_result['hits']['total']

        weibo = []

        for hit in es_result['hits']['hits']:
            res = extract_result_backend(hit, keywords + words) if param['backend'] else extract_result(hit)
            weibo.append(res)

        if param['backend']:
            user_ids = [w['user_id'] for w in weibo]
            user_engagements = self.engagement_info_querier.search({'user_ids': user_ids, 'size': 5000})
            for w in weibo:
                w['engagement_info'] = user_engagements.get(w['user_id'], {})

        return {
            'total': total,
            'keywords': keywords,
            'weibo': weibo,
            'ex_keywords': param.get('ex_keywords', []),
            'ex_category': param.get('ex_category', [])
        }

    def _gen_query(self, term, query_keywords, filters, filter_words, order, from_, size_, highlight):

        filter_clause = []
        if filters:
            filter_clause = self._add_filter_clause(filter_clause, filters, 'keywords')
            filter_clause = self._add_filter_range_clause(filter_clause, filters, 'avg_likes')
            filter_clause = self._add_filter_range_clause(filter_clause, filters, 'avg_retweets')
            filter_clause = self._add_filter_range_clause(filter_clause, filters, 'avg_comments')
            filter_clause = self._add_filter_range_clause(filter_clause, filters, 'avg_engagement')

            filter_clause = self._add_filter_range_clause(filter_clause, filters, 'min_likes')
            filter_clause = self._add_filter_range_clause(filter_clause, filters, 'min_retweets')
            filter_clause = self._add_filter_range_clause(filter_clause, filters, 'min_comments')
            filter_clause = self._add_filter_range_clause(filter_clause, filters, 'min_engagement')

            filter_clause = self._add_filter_range_clause(filter_clause, filters, 'engagement_ratio')

            filter_clause = self._add_filter_range_clause(filter_clause, filters, 'timestamp')
            filter_clause = self._add_filter_range_clause(filter_clause, filters, 'score')
            filter_clause = self._add_filter_range_clause(filter_clause, filters, 'fans')
            filter_clause = self._add_filter_match(filter_clause, filters, 'user_id')
            filter_clause = self._add_filter_match(filter_clause, filters, 'nick_name')
            filter_clause = self._add_filter_match(filter_clause, filters, 'description')
            filter_clause = self._add_filter_clause(filter_clause, filters, 'user_type', 'should')
            filter_clause = self._add_filter_clause_minus(filter_clause, filters, 'verified_type', 'should')

            # if verified_range:
            #     filters['verified_type'] = verified_range
            #     filter_clause = self._add_filter_range_clause(filter_clause, filters, 'verified_type')

            if filters.get('category'):
                filters['category'] = [uc.category_smzdm_2_encode(c) for c in filters['category']]
                filter_clause = self._add_filter_clause(filter_clause, filters, 'category', 'should')

            if filters.get('category_media'):
                filters['category_media'] = [uc.category_media_2_encode(c) for c in filters['category_media']]
                # filters['category_media_weight'] = [CATEGORY_CUTOFF]
                filter_clause = self._add_filter_clause(filter_clause, filters, 'category_media', 'should')
                # filter_clause = self._add_filter_range_clause(filter_clause, filters, 'category_media_weight')
            if order == 'star':
                filters['verified_reason'] = ['明星', '歌手', '演员', '作家', '车手', '运动员']
                filter_clause = self._add_filter_match(filter_clause, filters, 'verified_reason', 'should')

        query = {"query": {"bool": {}}}

        for words in filter_words:
            filter_clause = self.filter_keywords(filter_clause, words)

        if filter_clause:
            query['query']['bool']['filter'] = filter_clause

        if query_keywords.strip() or term.strip():
            query['query']['bool']['must'] = {
                'bool': {
                    'should': [
                        {
                            'match': {
                                'user_id': {
                                    'analyzer': 'whitespace',
                                    'query': term.strip()[0: MAX_CHARACTER],
                                    'boost': 20,
                                }
                            }
                        },
                        {
                            'match_phrase': {
                                'nick_name': {
                                    'query': term.strip()[0:MAX_CHARACTER],
                                    'boost': 5,
                                }
                            }
                        } if order != 'star' else {},
                        {
                            'match_phrase': {
                                'description': {
                                    'query': term.strip()[0:MAX_CHARACTER],
                                    'boost': 2,
                                }
                            }
                        } if order != 'star' else {},
                        {
                            'multi_match': {
                                'analyzer': 'whitespace',
                                'query': query_keywords,
                                'fields': ['keywords'],
                                'boost': 1
                                # 'minimum_should_match': MINIMUM_SHOULD_MATCH
                            }
                        },
                        {
                            'match': {
                                'verified_reason': {
                                    'query': term.strip()[0:MAX_CHARACTER],
                                    # 'minimum_should_match': MINIMUM_SHOULD_MATCH,
                                    'boost': 1,
                                }
                            }
                        },
                    ]
                }
            }

        query['sort'] = []
        if order == utils.ORDER_INFLUENCE:
            query['sort'] = [
                {'score': 'desc'},
                {'_score': 'desc'}
            ]
        elif order == utils.ORDER_RELATIVE:
            query['sort'] = [
                {
                    '_script': {
                        "type": "number",
                        "script": {
                            "lang": "painless",
                            "inline": "_score * Math.log(doc.fans.value + 100.0)"
                        },
                        "order": "desc"
                    },

                },
                {'avg_engagement': 'desc'}
            ]
        elif order == utils.ORDER_TIMESTAMP:
            query['sort'] = [
                {'timestamp': 'desc'},
                {'score': 'desc'}
            ]
        elif order == 'avg_likes':
            query['sort'] = [
                {'avg_likes': 'desc'},
                {'_score': 'desc'}
            ]
        elif order == 'avg_retweets':
            query['sort'] = [
                {'avg_retweets': "desc"},
                {'_score': 'desc'}
            ]
        elif order == 'avg_comments':
            query['sort'] = [
                {'avg_comments': 'desc'},
                {'_score': 'desc'}
            ]
        elif order == 'star':
            query['sort'] = [
                {
                    '_script': {
                        "type": "number",
                        "script": {
                            "lang": "painless",
                            "inline": "Math.log(_score + 3.0) * Math.log(doc.fans.value + 1.0)"
                        },
                        "order": "desc"
                    },
                },
            ]
        else:
            query['sort'] = [
                {
                    '_script': {
                        "type": "number",
                        "script": {
                            "lang": "painless",
                            "inline": "_score * Math.log(doc.fans.value + 1.0)"
                        },
                        "order": "desc"
                    },

                },
                {'avg_engagement': 'desc'}
            ]

        if highlight:
            query['highlight'] = {
                "pre_tags": ["<span class='keyword'>"],
                "post_tags": ["</span>"],
                "fields": {"keywords": {}, "nick_name": {}, "description": {}}
            }

        query['track_scores'] = True
        query['from'] = from_
        query['size'] = size_

        return query

    @staticmethod
    def _add_filter_match(must_clause, filters, key, cond='must'):
        if key in filters:
            if filters[key]:
                clause = []
                must_clause.append({
                    'bool': {cond: clause}
                })
                values = filters[key]
                if isinstance(values, str):
                    values = values.split(' ')
                for fk in values:
                    clause.append({'match': {key: {'query': fk, 'minimum_should_match': MINIMUM_SHOULD_MATCH}}})
        return must_clause

    @staticmethod
    def _add_filter_clause_minus(filter_clause, filters, key, cond='must'):
        if key in filters:
            if filters[key]:
                clause = []
                filter_clause.append({
                    'bool': {
                        cond: clause
                    }
                })
                for fk in filters[key]:
                    if fk > 0:
                        clause.append({'term': {key: fk}})
                    else:
                        clause.append({'range': {key: {"lte": fk, 'gte': fk}}})
        return filter_clause

    @staticmethod
    def _add_filter_clause(filter_clause, filters, key, cond='must'):
        if key in filters:
            if filters[key]:
                clause = []
                filter_clause.append({
                    'bool': {
                        cond: clause
                    }
                })
                for fk in filters[key]:
                    clause.append({'term': {key: fk}})
        return filter_clause

    @staticmethod
    def _add_filter_range_clause(filter_clause, filters, key):
        if key in filters:
            if filters[key]:
                clause = []
                filter_clause.append({
                    'bool': {
                        'must': clause
                    }
                })
                fk = filters[key]
                if not isinstance(fk, list) or len(fk) < 1:
                    pass
                else:
                    min_fk = fk[0]
                    if len(fk) >= 2:
                        max_fk = fk[1]
                    else:
                        max_fk = None
                    if min_fk is not None and min_fk != 'null' and min_fk != '':
                        clause.append({'range': {key: {"gte": min_fk}}})
                    if max_fk is not None and max_fk != 'null' and max_fk != '':
                        clause.append({'range': {key: {"lte": max_fk}}})
        return filter_clause

    @staticmethod
    def filter_keywords(must_clause, keywords):
        clause = []
        must_clause.append({
            'bool': {'should': clause}
        })
        for fk in keywords:
            q = {
                'match_phrase': {
                    "description": {
                        'query': fk,
                        'slop': 1,
                    },
                }}
            clause.append(q)

        q_keywords = {
            'multi_match': {
                'analyzer': 'whitespace',
                'query': ' '.join(keywords),
                'fields': ['keywords'],
                # 'minimum_should_match': ""
            }
        }
        clause.append(q_keywords)
        return must_clause


def extract_result(hit):
    source_ = hit['_source']
    kol_influence_score = source_['score']
    score_ = hit['_score']
    res = utils.extract_user_from_source(source_)

    res['score'] = kol_influence_score
    res['relative'] = score_
    keywords = source_['keywords'][0:10]

    highlight = hit.get('highlight')
    if highlight:
        h_user_name = highlight.get('nick_name')
        if h_user_name:
            res['user_name'] = h_user_name[0]
        h_keywords = highlight.get('keywords')
        if h_keywords:
            hk2 = [s.replace("<span class='keyword'>", '').replace("</span>", '') for s in h_keywords]
            if hk2:
                h_keywords += [k for k in keywords if k not in hk2][0:10]
                res['keywords'] = h_keywords
        h_user_info = highlight.get('description')
        if h_user_info:
            res['user_info'] = h_user_info[0]

    return res


def extract_result_backend(hit, in_keywords):
    source_ = hit['_source']
    kol_influence_score = source_['score']
    score_ = hit['_score']
    res = utils.extract_user_from_source(source_)

    res['score'] = kol_influence_score
    res['relative'] = score_
    keywords = source_['keywords']
    keywords_weight = source_['keywords_weight']

    kw_dict = dict(zip(keywords, keywords_weight))

    term_in_keywords = [i for i in keywords if i in in_keywords]
    len_term_in_keywords = len(term_in_keywords)

    res['term_in_keywords'] = term_in_keywords
    res['term_in_keywords_weight'] = [kw_dict.get(t) for t in term_in_keywords]
    res['len_term_in_keywords'] = len_term_in_keywords
    res['keywords'] = keywords

    return res
